#include<sys/socket.h>
#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>


void error_handling(char *message);

int main(int argc,char *argv[]){
    int serv_sock;
    int clnt_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
    
    char message[] = "hello World!";

    if(argc!=2){
        printf("Usage: %s <port>\n",argv[0]);
        exit(1);
    }
    // 自己用man手册去查，先man -k 东西 查他具体在哪个章节，然后再man 2 socket查找到对应的函数看他的头文件和解释

    // PF_INET和AF_INET是一个意思表示IPv4 Internet protocols
    // SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.就理解为TCP协议就好了
    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    // On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set to indicate the error.
    if(serv_sock==-1){
        error_handling("socket() error");
    }
    // 一个电脑通常不只一个ip。比如回环接口 127.0.0.1 有线网卡 192.168.1.10 无线网卡 192.168.1.11 甚至可能有公网地址 203.0.113.5 
    // 这里设置ip为ANY的意思就是说我不管你客户端把数据包发送到哪个ip上，只要最终落到这个机器的这个端口上我都接收。
    // 端口是相当于是进程开的一个口子，一个进程可以有很多的口子，但是一个口子只对应后面一个进程，就是说这个端口和ip地址是哪里是没有关系的，你从哪个ip来都是送到了主机这个整体，你无论从哪个ip来都可以进到你想去的端口去。
    
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    // 刚才创建的那个socket它只是有一些比如ipv4和tcp的设置但是他还没有绑定到任何的地址呢，此时操作系统不知道你这个socket代表的是哪个本地地址
    // 所以接下来需要bind
    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1){
        error_handling("bind() error");
    }
    // 绑定了以后这个socket和这个地址就对应起来了。这样到时候对应起来了以后，本来你数据包送到了主机的大门口里面了，然后tcp协议她负责解析然后内核找到对应的这个socket之后把解包的信息再给socket的一个什么地方（存数据的）然后应用再去消费。
    
    // 此时虽然说是绑定了起来，但是由于tcp协议他是面向连接的协议，客户端必须先完成三次握手才能正式收发数据，listen() 干的就是这件事：他会把 socket 从 CLOSED 状态改成 LISTEN 状态，
    // 让内核为这个 socket 建立 半连接队列（SYN 队列）和 全连接队列（accept 队列），专门用来暂存正在握手的连接；只有处于 LISTEN 状态的 socket，内核才会在收到 SYN 包时自动回复 SYN-ACK、完成握手，并把最终建好的连接放进 accept 队列，等待你的 accept() 去取。

    // 流程为：
    // 客户端 connect() 发送 SYN 到达服务器主机。
    // 内核收到 SYN，检查目的端口有没有 socket 正处于 LISTEN 状态：
    // 有 → 内核立刻回 SYN-ACK，并把这条“半连接”放进 SYN 队列（也叫半连接队列）。
    // 客户端回最后一个 ACK，握手完成。
    // 内核把该连接从 SYN 队列移到 accept 队列（全连接队列）。
    // 此时连接已 ESTABLISHED，但应用层还没有拿到它。
    // 应用层调用 accept()，内核从 accept 队列取出一个节点，返回新的已连接 socket fd，后续 read/write 都基于这个 fd。
    // 所以：
    // SYN 队列 → 握手中间态（SYN_RECEIVED）。
    // accept 队列 → 握手完成态（ESTABLISHED），等 accept() 领走。
    // backlog 参数就是限制 accept 队列的最大长度。


    // 所以如果你的客户端此时connect发过来那他应该也是以tcp数据包的形式发过来的吧？？？
    // 是的到达主机后，内核协议栈先解出“四元组”：(src_ip, src_port, dst_ip, dst_port)然后用 (dst_ip, dst_port) 去匹配本机的 socket hash 表找到对应的socket然后看他是不是listen状态的

    // 那是不是也是到达主机对应的ip以后再由tcp协议解析出来获得四元组然后找到对应的socket结果发现它此时如果不是listen状态那就不回复SYN-ACK那会发生什么呢？？？
    // 内核回一个 RST（tcp_rst），客户端的 connect() 立即返回 ECONNREFUSED。这就是“Connection refused”报文的来历。

    // 如果是listen的就回复SYN_ACK然后把这条连接加入半连接队伍，那这个所谓的“连接”是以什么形式存在的呢？？一个数据结构吗？包括什么内容呢？
    // 有 LISTEN socket 时，SYN 到达后的“连接”长什么样内核为它创建一个 request_sock（更准确地说是 struct inet_request_sock），主要字段：
    // struct request_sock {
    //     struct sock *sk;          // 指向监听父 sock
    //     u16 mss, snd_wscale;
    //     u32 saddr, daddr;
    //     u16 sport, dport;
    //     u8  tstamp_ok, sack_ok;
    //     u32 ts_recent;
    //     u32 cookie;               // SYN Cookie 用
    //     ...
    // }
    // 这就是“半连接”实体，挂到监听 sock 的 SYN 队列（内核叫 icsk_accept_queue.rskq_defer_accept 链表）里。它不是一条完整 struct sock，只是轻量级握手上下文，省内存。


    // 然后客户端收到以后回复一个ACK三次握手完毕，服务端内核把这个连接从SYN队列移动到accept队列(全连接队列）应用层调用accept()内核从accept队列取一个节点返回新的已连接的socketfd那原来的socketfd呢？？？就是listen的那个呢？？？
    // 客户端回 ACK 后，内核如何把半连接变“全连接”找到同一个 request_sock → 校验 ACK 号合法 → 分配真正的 子 struct sock（TCP 控制块，含接收/发送缓冲区、滑动窗口、定时器等）。
    // 把 request_sock 从 SYN 队列摘下，挂到监听 sock 的 accept 队列（sk->sk_ack_backlog 链表）。此时子 sock 状态 = ESTABLISHED；监听 sock 仍保持 LISTEN 状态，纹丝不动。


    // 应用层的accept是不是阻塞的呢？？？有没有可能下面还没做完三次握手应用就要accept了呢？
    // 如果 accept 队列空，默认阻塞（除非 listensock 设了 O_NONBLOCK，此时立即返回 -1 errno=EAGAIN）。非空 → 摘下一个子 sock，为它分配一个 新的 fd，返回给用户空间。之后 read/write 都用这个 newsock；原来的 listensock 继续负责“拉客”，永不会用来传业务数据。
    // accept() 只能摘已握手完毕的节点。只要三次握手还没完成（子 sock 还没进 accept 队列），accept() 就阻塞（或非阻塞返回 EAGAIN）。所以“应用层永远看不到半连接”，它看到的都是 ESTABLISHED 状态的子 socket。



    // 你这里面提到的request_sock还有给他移动到accept之类的，这里这个request_sock他是不是就是一个socket啊？？？
    // 不是“一条完整 socket”，而是专门为握手阶段设计的轻量级临时结构。struct request_sock 只有几十字节，里面只放握手必需字段：四元组、MSS、窗口扩大因子、初始序列号、cookie 等。真正的 struct sock（含发送/接收缓冲区、滑动窗口、重传队列、文件描述符等）还没分配。
    // 但是当最后一个ACK收到了以后会给他升级为真正的socket，所以说服务器那边accept以后才会拿到一个新的socket就是从这里来的。收到第三次 ACK 后，内核以该 request_sock 为模板，新建/初始化一个正式的 struct sock（含发送/接收缓冲区、窗口、重传队列等），状态置为 ESTABLISHED。
    // 把这条新 sock 挂到监听父 sock 的 accept 队列。应用层 accept() 取出时，内核给它分配一个新的文件描述符 (fd)，返回给用户。——你代码里拿到的 clnt_sock 正是这个“升级后”的 socket，而原来的 serv_sock 仍继续 LISTEN，不会用来传业务数据。


    // 总结
    // 所以总结一下就是说，客户端connect是发送SYN给客户端，当然这个会经过ip层传递到对应的主机然后通过tcp协议的解析解析出四元组然后根据它找到对应的socket判断是否是listen的如果是的话则接收端建立一个半连接的数据结构挂到监听socket的SYN队列里面，
    // 然后服务端按照三次握手那样回复一个SYN-ACK,客户端收到以后呢把客户端里面自己的这个socket给他状态改为ESTABLISHED状态然后给服务器发送一个ACK，应用层的connect此时返回0程序继续往下走，可能会发送数据，
    // 那这时就改变状态了你不怕你的最后回复的ACK过不去吗？？或者还没等过去呢？其实不会的，客户端的socket 提前 ESTABLISHED 后，内核已为它分配好 发送缓冲区（sk_write_queue）。
    // 应用第一次 send() 只是把数据 拷贝进这个缓冲区，立刻返回“成功写入 n 字节”；数据此时还躺在内存里，根本还没打包成 TCP 段。只有当客户端收到服务端发回的 ACK=y+1（即第三次握手完成）后，内核才：打开发送窗口（cwnd 初始值）
    // 把缓冲区里的数据按 MSS 切片、编号，开始真正发段如果 ACK 迟迟不到，发送窗口一直为 0，数据就继续停在缓冲区；那此时send如果是阻塞模式就睡觉，如果是非阻塞的呢就返回EGAIN。然后服务器收到最后一个ACK以后，
    // 服务器此时把这个reqeust_sock从SYN队列移动到accept队列（全连接），子socket状态改为ESTABLISHED等待被accept取走。他是如何能够准确的找到这个ACK对应的request_sock呢？因为一个监听 socket 可以同时存在成千上万条半连接怎么找到呢
    // 靠的是哈希 + 精确比对，首先把收到的报头四元组 (sip, sport, dip, dport) 做一次 Jenkins/hash_32 计算 → 得到哈希值 hash → 索引到全局 SYN 哈希表 syn_table[hash & mask]。每个表项是一条双向链表，链表上挂的全是哈希值相同的 request_sock（通常 0-3 个节点）。
    // 哈希表撞到同一个桶里是不可避免的即使四元组都不完全相同，遍历这条短链表，对每个节点执行：if (req->rmt_port == sport &&req->loc_port == dport &&req->rmt_addr == sip  &&req->loc_addr == dip  &&req->snt_isn + 1 == ack_seq)return req;          // 找到正主
    // 这样对比四元组每一个，那为啥还要seq呢？似乎有四元组就够了，其实不是，因为网络里可能飘着上一次断链的迟到 SYN 或 ACK；四元组碰巧一样，但序列号早已过期。内核必须检查 ack_seq == req->snt_isn + 1 且在合法窗口内，否则丢弃，避免把过期握手当成新连接。
    // 就记住了他再往下通过时间戳和其他的一些操作使得四元组相同序号还恰好满足几乎不可能出现，所以通过四元组加序号就可以正确定位到目标半连接了。


    



}

